#ifndef __tenacitas_time__algorithm__h__
#define __tenacitas_time__algorithm__h__

/** @file Functions that implement operations on
 * tenacitas::date objects
 */

// -----------------------------------------------------------------------------
//                                   C++ headers
// -----------------------------------------------------------------------------
#include <ctime>
#include <cstdint>
#include <exception>

// -----------------------------------------------------------------------------
//                                   3rds headers
// -----------------------------------------------------------------------------

// -----------------------------------------------------------------------------
//                                   our headers
// -----------------------------------------------------------------------------
//#include "date.h"


// -----------------------------------------------------------------------------
//                                   namespaces
// -----------------------------------------------------------------------------
using namespace std;
using namespace tenacitas;

// -----------------------------------------------------------------------------
//                                   macro constants
// -----------------------------------------------------------------------------

/** @def <MACRO-CONTANT-NAME> */
/** */

// -----------------------------------------------------------------------------
//                                   macro commands
// -----------------------------------------------------------------------------

/** @def <MACRO-COMMAND-NAME> */
/** */

// -----------------------------------------------------------------------------
//                                   typedefs
// -----------------------------------------------------------------------------

/** @typedef */
/** */

/** namespace of the organization */
namespace tenacitas {

    // ---------------------------------------------------------------------
    //                           pre-declarations
    // ---------------------------------------------------------------------

    // ---------------------------------------------------------------------
    //                          typedefs
    // ---------------------------------------------------------------------

/** @typedef */

    struct day
    {
        day( uint8_t p_value )
            : m_value( 1 ) {

            if ( ( p_value > 31 ) || ( p_value == 0 ) ) {
                throw std::exception( "Invalid value for day " );
            }

            m_value = p_value;
        }

        operator uint8_t( ) {
            return m_value;
        }


    private:
        uint8_t m_value;
    };


    /** */
    typedef enum { sun = 0, mon, tue, wed, thr, fri, sat } weekdays;

    /** */
    typedef enum { jan = 1, feb, mar, abr, may, jun, jul, ago, sep, oct, nov, dec } months;

    typedef uint16_t year;



    // ---------------------------------------------------------------------
    //                          declarations
    // ---------------------------------------------------------------------

    // ---------------------------------------------------------------------
    /** Function that informs if the a type of year is a leap year
     *
     * @return true if @p p_time is a leap year, false otherwise
     */
    inline bool is_leap ( const year & p_y ) {

        return ( p_y % 4  == 0 );
    }

    /** Informs if the a type of date is in a  weekend
     *
     *
     * @return true if @p p_time is is in a weekend, false otherwise
     */
    bool is_weekend ( const t_time & p_time ) {

        std::tm* l_time = std::gmtime( & p_time );

        return ( ( l_time->tm_wday == sat ) || ( l_time->tm_wday == sun ) );
    }

    // ---------------------------------------------------------------------
    /** Rounds a day, accourding to the original day, month and year
     *
     * @note The type parameters @p t_day, @p t_month and @p
     * t_year must conform to the tenacitas::day,
     * tenacitas::month and tenacitas::year.
     *
     * if m == feb, d = 29 or 30 and y is not leap, returns 28
     * if m == feb, d = 29 or 30 and y is     leap, returns 29
     * if m == abr, jun, sep or nov, and d = 31,    returns 30
     * if m any other,                              returns d
     */
    day round_day ( const day & d, const months & m, const year & y ) {
        day rc = d;

        if ( ( m == feb ) &&
             // february
             ( d >= 30 )
             // but the day is greater than 30
            ) {

            // so we must adjust the day
            if ( is_leap ( y ) ) {
                rc = t_day ( 29 );
            } else {
                rc = t_day ( 28 );
            }
        } else if ( ( d == 31 ) &&
                    // day is 31
                    ( ( m == abr ) || ( m == jun ) || ( m == sep ) || ( m == nov ) )
                    // but in a 30-days month
            ) {
            // so, we adjust the day
            rc = 30;
        }
        return rc;
    }

    // ---------------------------------------------------------------------
    /** @class shift
     * Functor that shifts a time a number of periods
     *
     * The @p t_time type parameter must implment the same methods as
     * tenacitas::date
     *
     * The @p t_shift_type indicates how many peridos will the time be
     * shifted. Presently (2009.03.07), we have tenacitas::day,
     * tenacitas::month, and tenacitas::year
     */
    template <typename t_shift_type>
    class shift {

        // public internal types
        typedef t_shift_type shift_type;

        // public operators
        time_t operator ( ) ( time_t & p_start, const uint16_t & p_amount = 1 ) const {
            return time( nullptr );
        }
    };

    // ---------------------------------------------------------------------
    /** @copydoc tenacitas::shift
     * Functor that shifts a time a number of days
     */
    template <typename t_time>
    class shift <typename t_time::day_type, t_time> {
    public:

        // -----------------------------------------------------------------
        // shift<t_time::day_type, t_time>  public internal types
        // -----------------------------------------------------------------
        typedef t_time time_type;

        typedef typename t_time::day_type shift_type;

        // -----------------------------------------------------------------
        // shift<t_time::day_type, t_time>  public operators
        // -----------------------------------------------------------------

        /** Shifts a a time a number of days forwards or
         * backwards
         *
         * @param d is the value of the initial time_type
         *
         * @param n is the number of days we want to shift
         * forward, if positive, or backwards, if negative
         */
        time_type operator ( ) ( const time_type & start, const i16t & n = 1 ) const
        throw ( fault ) {
            return time_type ( start.get_value ( ) + n );
        }
    };

    // ---------------------------------------------------------------------
    /** @copydoc tenacitas::shift
     * Functor that shifts a time a number of months
     */
    template <typename t_time>
    class shift <typename t_time::month_type, t_time> {
    public:
        // -----------------------------------------------------------------
        // shift<t_time::month_type, t_time> public internal types
        // -----------------------------------------------------------------

        typedef t_time time_type;
        typedef typename t_time::month_type shift_type;

        typedef typename t_time::day_type day_type;
        typedef typename t_time::month_type month_type;
        typedef typename t_time::year_type year_type;

        // -----------------------------------------------------------------
        // shift<t_time::month_type, t_time>  public operators
        // -----------------------------------------------------------------

        /** Shifts a time a number of months
         *
         * @param d is the value of the initial time_type
         *
         * @param n is the number of months we want to shift
         * forward, if positive, or backwards, if negative
         */
        time_type operator ( ) ( const time_type & d, const i16t & n = 1 ) const
        throw ( fault ) {

            time_type rc ( d );

            // avoiding to copy, if we can
            bool use_rc = true;

            if ( n == 0 ) {
                use_rc = false;
            } else {

                // how many years this number of months
                // represent?
                i16t ay = static_cast <i16t> ( n / 12 );

                if ( ay != 0 ) {
                    // the amount of years is more than 1, for
                    // more or for less

                    // so, we move forward/backward 1 year
                    shift <year_type, time_type> sy;
                    rc = sy ( d, ay );

                    // and reprocess the request, now knowing
                    // that the amount of months is less than
                    // 12
                    shift <month_type, time_type> sm;
                    rc = sm ( rc, n - ( ay * 12 ) );
                } else {
                    // the amount of years is 0

                    // saving the current month and year
                    month_type maux ( d.get_month ( ) );
                    year_type yaux ( d.get_year ( ) );

                    if ( n > 0 ) {
                        // we are moving forwards in time

                        // the current month plus the amount of months
                        // will produce a date in the next year, e.g.
                        // 2007.08.13 + 8 months?
                        i16t
                            naux =
                            static_cast <i16t> ( d.get_month ( ).get_value ( )
                                                 + n - 12 );

                        if ( naux > 0 ) {
                            // yes...

                            // so, we increase the year
                            yaux = year_type ( yaux.get_value ( ) + 1 );
                            // and set the new month
                            maux = month_type ( naux );
                        } else {
                            // no...

                            // so the new month is the current plus
                            // the i16t requested
                            maux = month_type ( d.get_month ( ).get_value ( )
                                                + n );
                        }
                    } else {

                        // we are moving backwards in time

                        // the current month minus the amount of
                        // months will produce a date in the next
                        // year, say 2007.01.13 + ( -8 ) months?
                        i16t
                            naux =
                            static_cast <i16t> ( d.get_month ( ).get_value ( )
                                                 + n );

                        if ( naux > 0 ) {
                            // no...

                            // so, we stay in the same year
                            yaux = year_type ( yaux.get_value ( ) );
                            // and set the new month
                            maux = month_type ( naux );
                        } else {
                            // yes...

                            // so, we move to the previous year
                            yaux = year_type ( yaux.get_value ( ) - 1 );
                            // and set the new month ( remember naux
                            // is negative )
                            maux = month_type ( 12 + naux );
                        }
                    }

                    // avoiding to create the invalid date of
                    // 2009.02.30 by shifting 1 month forward from
                    // 2009.30.01, for example
                    day_type daux = round_day ( d.get_day ( ), maux, yaux );

                    rc.set_day ( daux );
                    rc.set_year ( yaux );
                    rc.set_month ( maux );
                }
            }

            if ( use_rc ) {
                // a new value was actually created
                return rc;
            }

            // no new value was produced, so we return the original
            // one
            return d;
        }
    };

    // ---------------------------------------------------------------------
    /** @copydoc tenacitas::shift
     * Functor that shifts a time a number of years
     */
    template <typename t_time>
    class shift <typename t_time::year_type, t_time> {
    public:

        // -----------------------------------------------------------------
        // shift<t_time::year_type, t_time> public internal types
        // -----------------------------------------------------------------
        typedef t_time time_type;
        typedef typename t_time::year_type shift_type;

        typedef typename t_time::year_type year_type;
        typedef typename t_time::day_type day_type;

        // -----------------------------------------------------------------
        // shift<t_time::year_type, t_time>  public operators
        // -----------------------------------------------------------------

        /** Shifts the date a number of years
         *
         * @param d is the value of the initial time_type
         *
         * @param n is the number of years we want to shift
         * forward, if positive, or backwards, if negative
         */
        time_type operator ( ) ( const time_type & d, const i16t & n = 1 ) const
        throw ( fault ) {
            time_type rc ( d );

            year_type yaux ( d.get_year ( ) + n );

            day_type daux = round_day ( d.get_day ( ), d.get_month ( ), yaux );

            rc.set_day ( daux );
            rc.set_year ( yaux );
            return rc;
        }
    };

    // ---------------------------------------------------------------------
    /** @class simple_time_creator
     * Simply creates a @p t_time object, with no further
     * processing
     */
    //     template <typename t_time>
    //     class simple_time_creator {
    //     public:
    // 	 // -----------------------------------------------------------------
    // 	 // recurrence  public internal types
    // 	 // -----------------------------------------------------------------
    // 	typedef t_time time_type;

    // 	 // -----------------------------------------------------------------
    // 	 // simple_time_creator  public operators  
    // 	 // -----------------------------------------------------------------


    // 	 /** Simply creates a @p t_time object, with no further
    // 	* processing
    // 	*
    // 	* @param [in] d is a @p day_type object, which defines the day for
    // 	* the @p t_time object to be created
    // 	*
    // 	* @param [in] m is a @p month_type object, which defines the month for
    // 	* the @p t_time object to be created
    // 	*
    // 	* @param [in] y is a @p year_type object, which defines the year for
    // 	* the @p t_time object to be created
    // 	*
    // 	* @throw fault if the @p t_time created is invalid
    // 	t_time
    // 	operator ( ) ( const typename t_time::day_type & d,
    // 		       const typename t_time::month_type & m,
    // 		       const typename t_time::year_type & y) const
    // 	throw ( fault )  {
    // 	    return t_time ( d, m, y ) ;
    // 	}
    //     };


    // 	 /** Calculates the number of days between two dates
    // 	i16t
    // 	diff_days( const date &d1,
    // 		   const date &d2) ;

    // 	 /** Calculates the number of months between two dates
    // 	i16t
    // 	diff_months( const date &d1,
    // 		     const date &d2) ;

    // 	 /** Calculates the number of years between two dates
    // 	i16t
    // 	diff_years( const date &d1,
    // 		    const date &d2) ;


}

#endif 

// 	 /** Retrieves the tenacitas::weekday for a
// 	* given tenacitas::date 
// 	weekday get_weekday( date & d ); 

// 	 /** Returns true if it a tenacitas::date id
// 	* saturday or sunday  
// 	bool is_weekend( date & d ); 

// 	 /** Returns the date corresponding to a certain next weekday 
// 	* 
// 	* For example,  
// 	* 
// 	* @code 
// 	* date d0( 16, month::abr, 2008 ); 
// 	* date d1 = next_weekday( d0, weekday::sat ); 
// 	* cout << "d1 = " << d1 << endl; 
// 	* @endcode 
// 	* 
// 	* This code would print 19/abr/2008 
// 	date next_weekday( const date & d,
// 				      const weekday & wd ); 

// 	 /** Returns the number of days to the end of week,
// 	* which is always saturday
// 	i16t to_end_week( const date & d ); 

// 	 /** Returns the number of days to the end of month,
// 	* which is always saturday
// 	i16t to_end_month( const date & d ); 

// 	 /** Returns the number of days to the end of the year, 
// 	* which is always saturday
// 	i16t to_end_year( const date & d ); 

// 	 /** Returns the last day of the month
// 	date last_in_month( const date & d ); 

// 	 /** Returns the last day of the week
// 	date last_in_week( const date & d ); 

// 	 /** Returns the last day of the year
// 	date last_in_year( const date & d ); 

// 	 /** Shifts the date a number of days
// 	date
// 	shift_days( const date & d,
// 		    const i16t & n = 1 )
// 	    throw ( fault );


// 	 /** Shifts the date a number of years
// 	date
// 	shift_years( const date & d,
// 		     const i16t &n = 1 ) 
// 	    throw ( fault );

// 	 /** Calculates the number of days between two dates
// 	i16t
// 	diff_days( const date &d1,
// 		   const date &d2) ;

// 	 /** Calculates the number of months between two dates
// 	i16t
// 	diff_months( const date &d1,
// 		     const date &d2) ;

// 	 /** Calculates the number of years between two dates
// 	i16t
// 	diff_years( const date &d1,
// 		    const date &d2) ;

// 	 /** Rounds a day, accourding to the original day,
// 	* month and year
// 	*
// 	* if m == feb, d = 29 or 30 and y is not leap, returns 28
// 	* if m == feb, d = 29 or 30 and y is     leap, returns 29
// 	* if m == abr, jun, sep or nov, and d = 31,    returns 30
// 	* if m any other,                              returns d
// 	day round_day (const day & d,
// 				  const month & m,
// 				  const year & y);

// 	 /** Rounds a date, accourding to the original day,
// 	* month and year
// 	*
// 	* if m == feb, d = 29 or 30 and y is not leap, returns 28
// 	* if m == feb, d = 29 or 30 and y is     leap, returns 29
// 	* if m == abr, jun, sep or nov, and d = 31,    returns 30
// 	* if m any other,                              returns d
// 	date round_date ( const day & d,
// 				     const month & m,
// 				     const year & y );


// 	 /** Searches for a day in a sequence which is the first greater
// 	* than a day in a date, and creates a new date with this day
// 	*
// 	* If there is a <tt> d.get_day( ) </tt> in @p sequence, it retunrs
// 	* @p d;
// 	* Otherwise, it returns a date which day is the next in the
// 	* sequence.
// 	* If <tt> d.get_day( ) </tt> is greather then <tt> *sequence.end( )
// 	* </tt>, the it returns a date which day is the <tt> *sequence.begin( )
// 	* </tt>, forwarded one month
// 	date
// 	first_after(const date & d,
// 		    const days & sequence);

// 	 /** Returns true if @p candidate is in @p but_not_in
// 	bool
// 	in_no_day_list(const date & candidate,
// 		       const ptr<dates> but_not_in)


// 	 // ---------------------------------------------------------------------
// 	 /** Functor that calculates the last date of a period
// 	* of time
// 	*
// 	* @note The @p t_time_time must 
// 	template <typename t_time, typename t_frequency_type>
// 	t_time last_date  ( const t_time & t )
// 	    throw ( fault ) {return t_time( ) ;}


// 	 // ---------------------------------------------------------------------
// 	template <typename t_time, typename t_frequency_type>
// 	t_time last_date <t_time, month> ( const t_time & d ) 
// 	throw ( fault ) {

// 	    t_time rc ( d ) ;

// 	    month m ( d.get_month ( ) ) ;
// 	    year y ( d.get_year ( ) ) ;

// 	    if ( ( m == month::abr ) ||
// 		 ( m == month::jun ) ||           
// 		 ( m == month::sep ) ||
// 		 ( m == month::nov ) ) {
// 		rc.set_day ( 30 ) ;
// 	    }
// 	    else if ( m == month::feb ) {
// 		if ( is_leap ( y ) ) {
// 		    rc.set_day ( 29 ) ;
// 		}
// 		else {
// 		    rc.set_day ( 28 ) ;
// 		}
// 	    }
// 	    else {
// 		rc.set_day ( 31 ) ;
// 	    }
// 	    return rc;
// 	}

// 	 // ---------------------------------------------------------------------
// 	template <typename t_time>
// 	t_time last_date <t_time, year> ( const t_time & d ) 
// 	throw ( fault ) {

// 	    t_time rc ( d ) ;
// 	    d.set_month ( month::dec ) ;
// 	    d.set_day ( 31 ) ;

// 	    return rc;
// 	}

// 	 // ---------------------------------------------------------------------
// 	* @todo Implement  last_date <weekday>
// 	template <typename t_time>
// 	date last_date <t_time, weekday> ( const date & d ) 
// 	throw ( fault ) {
// 	    return date::today ( ) ;
// 	}
